#ifndef TPL_SIM_AGENT_GRAPH_H
#define TPL_SIM_AGENT_GRAPH_H

#include <iostream>
#include <sys/types.h>
#include <dlink.H>
#include <tpl_concurrent_graph.H>
#include <tpl_dynArray.H>
#include <tpl_arrayQueue.H>
#include <tpl_dynBinHeap.H>


using namespace Aleph;
using namespace std;

namespace Aleph {

#define CREATE_EVENT 1001
#define DESTROY_EVENT 1002
#define SELECT_ARC_EVENT 1003
#define NODE_ARRIVAL_EVENT 1004

#define STOPPED 0
#define RUNNING 1
#define SUSPENDED 2


  template <typename Node_Info> class Sim_Agent_Node;  
  
  template <typename Arc_Info> class Sim_Agent_Arc;
  
  template <typename Agent_Node, typename Agent_Arc> class Sim_Agent_Graph;

  class Sim_Agent   
  {
    int id;
  public:
    Dlink agent_link;
    Dlink location_link;
    bool in_node;
    void * location;
    void * cookie;
    
    Sim_Agent(int _id) : id(_id) { }
    
    virtual ~Sim_Agent(){ }
 
    int get_id()
    {
      return id;
    }

    virtual void arrive_to_node() 
    {
      //TODO: Implement this method
    }

    virtual void select_arc()
    {
      //TODO: Implement this method
    }

    LINKNAME_TO_TYPE (Sim_Agent, agent_link);  
    LINKNAME_TO_TYPE (Sim_Agent, location_link);

 };  
  
  struct Sim_Event
  {
    int event_type;
    long actual_time;
    Sim_Agent * agent;
    void * cookie;

    const bool operator < (const Sim_Event & event) const
    {
      return actual_time < event.actual_time;    
    }
  };

  class Sim_Event_Type
  {
    int event_type_code;
    void (*Event_Execute) ()
  }

 template <typename Agents_Node_Info> 
  struct Sim_Agent_Node :  public Concurrent_Node <Agents_Node_Info>
  {
    Dlink agent_list;
    typedef Concurrent_Node<Agents_Node_Info> Parent_Node;
    typedef Agents_Node_Info Node_Type;
    Sim_Agent_Node() : Parent_Node() { }
    Sim_Agent_Node(const Node_Type & info) : Parent_Node(info) {}
    ~Sim_Agent_Node(){ }
  }; 
  
  
  template <typename Agents_Arc_Info> 
  struct Sim_Agent_Arc : public Concurrent_Arc<Agents_Arc_Info>    {
    Dlink agent_list;
    typedef Concurrent_Arc<Agents_Arc_Info> Parent_Arc;
    typedef Agents_Arc_Info Arc_Type;
    Sim_Agent_Arc() : Parent_Arc() { } 
    virtual ~Sim_Agent_Arc(){ } 
  };
  
  template <typename Node, typename Arc> 
  struct Sim_Agent_Graph : public Concurrent_Graph<Node, Arc>
  {
   public:
    typedef Concurrent_Graph< Node, Arc > Parent_Graph;
    typedef typename Node::Node_Type Node_Type; 
    typedef typename Arc::Arc_Type Arc_Type;

   private:
    size_t thread_count;
    pthread_t * threads;
    int status ;
    pthread_mutex_t sch_mutex;
    pthread_mutex_t suspend_mutex;
    pthread_cond_t sch_cond;
    pthread_cond_t suspend_cond;
    long actual_time;
    long callback_rate;
    typedef void (* Update_Callback) (Sim_Agent_Graph *, Sim_Agent *, void *);
    Update_Callback update_callback; 
    

    /** Dlinks a las listas de nodos arcos y agentes **/
    
    Dlink agent_list; // lista de agentes
    long num_agents;
   
    /** Heap de eventos**/
    DynBinHeap<Sim_Event*> events_heap;
    
    
    /** Metodos para los nodos **/ 
   
  public:
   /**Funciones de los agentes **/
    const long get_num_agents() const {return num_agents;}
   
    Node * get_agent_node_location (Sim_Agent * agent) 
    {
      if (agent->in_node) 
      {
        return static_cast<Node*>(agent->location);
      }
      return NULL;
    }

    void set_agent_node_location (Sim_Agent * agent, Node * node)
    {
      agent->location_link.del();
      agent->in_node = true;
      agent->location = node;
      node->agent_list.append(&agent->location_link);
    }

    Arc * get_agent_arc_location (Sim_Agent * agent)
    {
      if (!agent->in_node) 
      {
        return static_cast<Arc*>(agent->location);
      }
      return NULL;
    }

    void set_agent_arc_location(Sim_Agent * agent, Arc * arc)
    {
      agent->location_link.del();
      agent->in_node = false;
      agent->location = arc;
      arc->agent_list.append(&agent->location_link);
    }

    bool is_agent_in_node (Sim_Agent * agent)
    {
      return agent->in_node;
    }

    Sim_Agent * get_first_agent ()
    {
      if(get_num_agents() == 0)
	      throw std::range_error("Graph has not agents ");
      return Sim_Agent::agent_link_to_Sim_Agent(&*agent_list.get_next());
    }
    
    void remove_agent (Sim_Agent * agent)
    { 
	    agent->agent_link.del();
      agent->location_link.del();
    }
   
    void clear_agent_list()
    {
      Sim_Agent * agent = NULL;
      for (Dlink::Iterator it(&agent_list); it.has_current(); )
      {
        agent = Sim_Agent::agent_link_to_Sim_Agent(it.get_current());
        it.next();
        remove_agent(agent);
        delete agent;
      }
    }

    /**Funciones para el Grafo **/
   
    Sim_Agent_Graph(const size_t num_threads) : thread_count(num_threads) 
    {
      threads = (pthread_t*)malloc(num_threads * sizeof(pthread_t));
      init_mutex(sch_mutex);
      init_mutex(suspend_mutex);
      pthread_cond_init(&suspend_cond, NULL);
      pthread_cond_init(&sch_cond, NULL);
      status =RUNNING; 
    }

    virtual ~Sim_Agent_Graph()
    {
      clear_agent_list();
      destroy_mutex(sch_mutex);
      destroy_mutex(suspend_mutex);
      pthread_cond_destroy(&suspend_cond);
      pthread_cond_destroy(&sch_cond);
    	free(threads); 
    } 
    
    void insert_agent_in_node(Sim_Agent * agent, Node * node )
    {
      //TODO: Change this method
      agent_list.append(&agent->agent_link);
      set_agent_node_location(agent, node);
      
      //1 Crear el objeto Agent(Asignar los valores )

      //2 Colocarlo en la cola correspondiente (meter en la cola del nodo)
      //meter en la cola de ejecucion
      //si hay hilos diponibles despertarlo.
    }
    
    void insert_agent_in_arc(Sim_Agent * agent, Arc * arc )
    {
      //TODO: Change this method
      agent_list.append(&agent->agent_link);
      set_agent_arc_location(agent, arc);
      //1 Crear el objeto Agent(Asignar los valores )
      //2 Colocarlo en la cola correspondiente (meter en la cola del nodo)
      //meter en la cola de ejecucion
      //si hay hilos diponibles despertarlo.
    }
    
    Sim_Agent * search_agent(int agent_id)
    {
      Sim_Agent * result = NULL;
      for (Dlink::Iterator it(agent_list); it.has_current(); it.next()) 
      {
        result = Sim_Agent::agent_link_to_Sim_Agent(it.get_current());
        if (result->get_id() == agent_id) 
        {
          return result;
        }
        
      }
      return NULL;
    }
   
    void start_graph() 
    {
      for (int i = 0; i < thread_count; i++) 
	    {
	      int result = pthread_create(&threads[i] , NULL, Sim_Agent_Graph::run, this);
	      if (result == 0) 
		    {
		      cout<<" Thread "<<i<<endl; 
		    }
	    }
    }

    void stop_graph()
    {
      if (get_status() == SUSPENDED)
      {
        resume_graph();
      }
      set_status(STOPPED);
      for (int i = 0; i < thread_count; i++) 
	    {
	      pthread_join(threads[i] , NULL);
      }
    }
    
    int get_status()
    {
      CRITICAL_SECTION(sch_mutex);
      return status;
    }

    void set_status(int value)
    {
      CRITICAL_SECTION(sch_mutex);
      status = value;
    }


    void suspend_graph()
    {
      set_status(SUSPENDED);
    }
    void resume_graph()
    {
      set_status(RUNNING);
      pthread_cond_broadcast(&suspend_cond);

    }

    void cancel_agent_event(Sim_Agent * agent)
    {
      //TODO  Implementar este método
      //Buscar eventos asociados a este agente en el heap
      //Eliminarlos del heap
    }

    void cancel_event(Sim_Event * event)
    {
    }

    void schedule_event (Sim_Event * event)
    {
      //TODO Implementar este metodo
      //Insertarlo en el heap

    }

    void set_update_callback(Update_Callback fnct)
    {
      //TODO Implementar este metodo
      update_callback = fnct;
    }

    long get_actual_time()
    {
      return actual_time;
    }

    void set_callback_rate(long rate)
    {
      callback_rate = rate;
    }

    long get_callback_rate()
    {
      return callback_rate();
    }
    
    static void * run(void * cookie) 
    {
      Sim_Agent_Graph * graph = static_cast<Sim_Agent_Graph*>(cookie);
      // Lógica de ejecución de agentes
      while (graph->get_status() != STOPPED)
      {
        while (graph->get_status() == SUSPENDED)
        {
          pthread_mutex_lock(&graph->suspend_mutex);
          pthread_cond_wait(&graph->suspend_cond, &graph->suspend_mutex);
          pthread_mutex_unlock(&graph->suspend_mutex);
        }
	      Sim_Event * next_event = NULL;
        do {
          //TODO Obtener proximo evento desde el heap
          next_event = NULL;
          if (next_event == NULL) 
          {
            pthread_mutex_lock(&graph->sch_mutex);
            pthread_cond_wait(&graph->sch_cond, &graph->sch_mutex);
            pthread_mutex_unlock(&graph->sch_mutex);
          }
	      } while (next_event == NULL);
        
        //TODO Ejecutar evento
        //TODO Establecer hora actual
        //TODO Si ha pasado tiempo > callback_rate, ejecutar update_callback
        //TODO Incluir evento en el heap
      }
      return NULL;
    }
  };
}
#endif 
